package som_alg;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Random;

/**
 * this class runs the algorithm of SOM
 */
public class Alg
{

	ArrayList<InputVector> inputs;
	int rows = 12;
	int cols = 12;
	int dna_size = 100;
	int input_size;
	int first_percent = 80;
	int secnd_percent = 70;
	int third_percent = 60;
	int final_round = 100;

	/**
	 * this function is the default constructor of this class
	 * @param inputs
	 */
	public Alg(ArrayList<InputVector> inputs) 
	{
		super();
		this.inputs = inputs;
		input_size = inputs.size();
	}

	/**
	 * this function run the main part of the algorithm
	 * @throws IOException
	 */
	public void run_alg() throws IOException
	{
		int i1,j1;
		ArrayList<ArrayList<String>> map = init_map();
		ArrayList<Integer> rep_location;
		int round = 0;

		while(round != final_round)
		{
			for (int i = 0; i < input_size; i++) 
			{
				// find this input dna string representative in the map
				rep_location = findRepresente(inputs.get(i), map);
				i1 = rep_location.get(0);
				j1 = rep_location.get(1);
				// update this cell and it's neighbors (two circles)
				updateNeighbour(map, i1, j1, inputs.get(i), true, null);
			}	
			round++;
		}
		// create result files with analasize of the result
		CheckClusters(map);
	}

	/*
	 * this function find the 12 clusters centers of the result map and creates the relevant files
	 * @param map - the result map
	 * @throws IOException
	 */
	private void CheckClusters(ArrayList<ArrayList<String>> map) throws IOException
	{
		ArrayList<ArrayList<Integer>> distances = new ArrayList<ArrayList<Integer>>();
		ArrayList<Integer> row;

		// for every cell in the map
		for (int i = 0; i < rows; i++) 
		{
			row = new ArrayList<Integer>();
			for (int j = 0; j < cols; j++) 
			{
				// find it's average distance from it's neighbors
				row.add(calcDistance(i,j,map));
			}
			distances.add(row);
		}

		ArrayList<ArrayList<Integer>> clusters = new ArrayList<ArrayList<Integer>>();
		ArrayList<Integer> minIndex;
		// for every cluster center (12)
		for (int i = 0; i < rows; i++) 
		{
			minIndex = findMin(distances,clusters, i+1);
			clusters.add(minIndex);
		}

		ArrayList<ArrayList<Integer>> mapedMap = new ArrayList<ArrayList<Integer>>();
		ArrayList<Integer> tempMap;

		// for every cell in the map
		for (int i = 0; i < rows; i++) 
		{
			tempMap = new ArrayList<Integer>();
			for (int j = 0; j < cols; j++) 
			{
				// find this cell - cluster center
				tempMap.add(findMyCluster(map,clusters,i,j));
			}
			mapedMap.add(tempMap);	
		}

		// Create the result file 
		FileWriter fstream2 = new FileWriter("map.csv");
		BufferedWriter out2 = new BufferedWriter(fstream2);

		// for each row in the arr array
		for (int ind1 = 0; ind1 < mapedMap.size(); ind1++) 
		{
			// for each column in the arr array
			for (int ind2 = 0; ind2 < mapedMap.get(ind1).size(); ind2++) 
			{
				// write the data into the file
				out2.write(mapedMap.get(ind1).get(ind2) + ",");
			}
			out2.write('\n');
		}

		out2.close();
		fstream2.close();


		ArrayList<ArrayList<String>> map2 = new ArrayList<ArrayList<String>>();
		ArrayList<String> map2_temp;
		HashMap<Integer, String> hashMap = new HashMap<Integer, String>();
		int temp_i, temp_j, temp_clusterNum, num;

		// for every cluster center
		for (int i = 0; i < clusters.size(); i++) 
		{
			temp_i = clusters.get(i).get(0);
			temp_j = clusters.get(i).get(1);
			temp_clusterNum = clusters.get(i).get(2);
			// put the string of the cluster center under it's number
			hashMap.put(temp_clusterNum, map.get(temp_i).get(temp_j));
		}

		// for every cell in the mapped map
		for (int i = 0; i < mapedMap.size(); i++) 
		{
			map2_temp = new ArrayList<String>();
			for (int j = 0; j < mapedMap.size(); j++) 
			{
				num = mapedMap.get(i).get(j);
				// create the strings map of clusters centers (every cell contains it's cluster center dna string)
				map2_temp.add(hashMap.get(num));
			}
			map2.add(map2_temp);
		}

		//create output file 
		FileWriter fstream3 = new FileWriter("output.txt");
		BufferedWriter out3 = new BufferedWriter(fstream3);

		InputVector in;
		ArrayList<Integer> cluster;

		// for each vector input
		for (int ind = 0; ind < input_size; ind++) 
		{
			in = inputs.get(ind);
			// get this vector cluster center
			cluster = findMyClusterForInput(map, clusters, in.getDna());
			out3.write(in.getId() + "	" + cluster.get(2) + "	" + cluster.get(0) + "	" + cluster.get(1));
			out3.newLine();
		}

		out3.close();
		fstream3.close();
	}

	/*
	 * this function find the cluster of the i,j cell in map from the clusters centers in clusters array
	 * @param map - the final result map
	 * @param clusters - the clusters centers
	 * @param i
	 * @param j
	 * @return - the cluster center number
	 */
	private Integer findMyCluster(ArrayList<ArrayList<String>> map, ArrayList<ArrayList<Integer>> clusters, int i, int j) 
	{
		int i1,j1, clusterNum, min=101,min_clusterNum = 0, count;
		String temp;

		String dna = map.get(i).get(j);

		// for every cluster center
		for (int k = 0 ; k < clusters.size() ; k++) 
		{
			i1 = clusters.get(k).get(0);
			j1 = clusters.get(k).get(1);
			clusterNum = clusters.get(k).get(2);

			temp = map.get(i1).get(j1);
			// get the distance
			count = getHammingDistance(dna, temp);

			// the case that a new minimum distance is found
			if (count < min)
			{
				min = count;
				min_clusterNum = clusterNum;
			}
		}
		return min_clusterNum;			
	}

	/*
	 * this function find the cluster of the dna string from the clusters centers in clusters array
	 * @param map - the result map
	 * @param clusters - the clusters centers
	 * @param dna - input dna string
	 * @return - information about this dna string result cluster center
	 */
	private ArrayList<Integer> findMyClusterForInput(ArrayList<ArrayList<String>> map, ArrayList<ArrayList<Integer>> clusters, String dna) 
	{
		int i1,j1, clusterNum = 0, min=101, min_clusterNum = 0, count, min_i = 0, min_j = 0;
		String temp;
		ArrayList<Integer> cluster = new ArrayList<Integer>();

		// for each cluster center
		for (int k = 0 ; k < clusters.size() ; k++) 
		{
			i1 = clusters.get(k).get(0);
			j1 = clusters.get(k).get(1);
			clusterNum = clusters.get(k).get(2);

			temp = map.get(i1).get(j1);

			// get the distance
			count = getHammingDistance(dna, temp);
			// the case that there is a new minimum distance
			if (count < min)
			{
				min = count;
				min_i = i1;
				min_j = j1;
				min_clusterNum = clusterNum;
			}
		}

		cluster.add(min_i);
		cluster.add(min_j);
		cluster.add(min_clusterNum);
		return cluster;			
	}

	/*
	 * this function find the minimum cell in distances matrix 
	 * @param distances - a matrix of distances
	 * @param previewsMins - the older indexes of the minimum cells
	 * @param clusterNum - the cluster number
	 * @return - the index in the map of the minimum distance cell
	 */
	private ArrayList<Integer> findMin(ArrayList<ArrayList<Integer>> distances,ArrayList<ArrayList<Integer>> previewsMins, int clusterNum) 
	{
		ArrayList<Integer> minIndex = new ArrayList<Integer>();
		ArrayList<Integer> temp;
		int min = 1000;
		int min_i = 0, min_j = 0;

		// for every cell in the distance matrix
		for (int i = 0; i < distances.size(); i++) 
		{
			for (int j = 0; j < distances.size(); j++) 
			{
				// the case that this cell is the minimum cell and doesn't close to an older minimum cell
				if ((distances.get(i).get(j) < min) && (!isClosest(i,j,previewsMins)))
				{
					min = distances.get(i).get(j);
					min_i = i;
					min_j = j;
				}
			}
		}

		temp = distances.get(min_i);
		// making it to not be the minimum cell next time
		temp.set(min_j, 1001);
		distances.set(min_i, temp);
		minIndex.add(min_i);
		minIndex.add(min_j);
		minIndex.add(clusterNum);
		return minIndex;
	}

	/*
	 * this function checks if the i,j cell is close to any of the other indexes in previewsMins  
	 * @param i
	 * @param j
	 * @param previewsMins - the preview indexes of the minimum cells
	 * @return - true if the i,j cell is close to any of the other indexes
	 */
	private boolean isClosest(int i, int j,ArrayList<ArrayList<Integer>> previewsMins) 
	{
		int temp_i, temp_j;

		// the case that this is not the first minimum found
		if (!previewsMins.isEmpty())
		{
			// for every index in the previewMins
			for (int k = 0; k < previewsMins.size(); k++) 
			{
				temp_i = previewsMins.get(k).get(0);
				temp_j = previewsMins.get(k).get(1);

				// the case that the i,j cell is close to temp_i, temp_j cell
				if(((temp_i == i-1)&&(temp_j == j-1)) || ((temp_i == i-1)&&(temp_j == j)) || ((temp_i == i-1)&&(temp_j == j+1)) ||
						((temp_i == i)&&(temp_j == j-1)) || ((temp_i == i)&&(temp_j == j+1)) || ((temp_i == i+1)&&(temp_j == j-1)) || 
						((temp_i == i+1)&&(temp_j == j)) || ((temp_i == i+1)&&(temp_j == j+1)))
				{
					return true;
				}
			}
		}
		return false;
	}

	/*
	 * this function calculate the average hamming distance of this cell from it's neighbors 
	 * @param i
	 * @param j
	 * @param map - the result map
	 * @return - the average distance
	 */
	private Integer calcDistance(int i, int j, ArrayList<ArrayList<String>> map)
	{
		int counter=0;

		int row = rows-1;
		int col = cols-1;
		String dna = map.get(i).get(j);

		// the case that the representative is in an even row 
		if(i%2 == 0)
		{
			// the case that this cell don't have all of the 8-usual neighbors (an edge cell)
			if(i==0 || i==row || j==0 || j==col)
			{
				// the case that this cell is in the first row
				if(i==0)
				{
					// the case that this cell is in the first column
					if(j==0)
					{
						counter += getHammingDistance(dna, map.get(i).get(j+1));
						counter += getHammingDistance(dna, map.get(i+1).get(j+1));
						counter += getHammingDistance(dna, map.get(i+1).get(j));
						return counter/3;
					}
					// the case that this cell is not in the first row
					else
					{
						// the case that this cell is in the last column
						if(j==col)
						{
							counter += getHammingDistance(dna, map.get(i).get(j-1));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							return counter/2;
						}
						// the case that this cell is not in the last column
						else
						{
							counter += getHammingDistance(dna, map.get(i).get(j+1));
							counter += getHammingDistance(dna, map.get(i+1).get(j+1));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							counter += getHammingDistance(dna, map.get(i).get(j-1));
							return counter/4;
						}
					}
				}
				// the case that this cell is not in the first row
				else
				{
					// the case that this cell is in the last row
					if(i==row)
					{
						// the case that this cell is in the first column
						if(j==0)
						{
							counter += getHammingDistance(dna, map.get(i-1).get(j));
							counter += getHammingDistance(dna, map.get(i-1).get(j+1));
							counter += getHammingDistance(dna, map.get(i).get(j+1));
							return counter/3;
						}
						// the case that this cell is not in the first column
						else
						{
							// the case that this cell is in the last column
							if(j==col)
							{
								counter += getHammingDistance(dna, map.get(i-1).get(j));
								counter += getHammingDistance(dna, map.get(i).get(j-1));
								return counter/2;
							}
							// the case that this cell is not in the last column
							else
							{
								counter += getHammingDistance(dna, map.get(i-1).get(j));
								counter += getHammingDistance(dna, map.get(i-1).get(j+1));
								counter += getHammingDistance(dna, map.get(i).get(j+1));
								counter += getHammingDistance(dna, map.get(i).get(j-1));
								return counter/4;
							}
						}
					}
					// the case that this cell is not in the last row
					else
					{
						// the case that this cell is in the first column
						if(j==0)
						{
							counter += getHammingDistance(dna, map.get(i-1).get(j));
							counter += getHammingDistance(dna, map.get(i-1).get(j+1));
							counter += getHammingDistance(dna, map.get(i).get(j+1));
							counter += getHammingDistance(dna, map.get(i+1).get(j+1));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							return counter/5;
						}
						// the case that this cell is not in the first column - it's in the last column
						else
						{
							counter += getHammingDistance(dna, map.get(i-1).get(j));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							counter += getHammingDistance(dna, map.get(i).get(j-1));
							return counter/3;
						}
					}
				}
			}
			// the case that this cell have all of the 8-usual neighbors
			else
			{
				counter += getHammingDistance(dna, map.get(i-1).get(j));
				counter += getHammingDistance(dna, map.get(i-1).get(j+1));
				counter += getHammingDistance(dna, map.get(i).get(j+1));
				counter += getHammingDistance(dna, map.get(i+1).get(j+1));
				counter += getHammingDistance(dna, map.get(i+1).get(j));
				counter += getHammingDistance(dna, map.get(i).get(j-1));
				return counter/6;
			}
		}
		// the case that the representative is in an odd row 
		else	
		{
			// the case that this cell don't have all of the 8-usual neighbors (an edge cell)
			if(i==0 || i==row || j==0 || j==col)
			{
				// the case that this cell is in the first row
				if(i==0)
				{
					// the case that this cell is in the first column
					if(j==0)
					{
						counter += getHammingDistance(dna, map.get(i).get(j+1));
						counter += getHammingDistance(dna, map.get(i+1).get(j));
						return counter/2;
					}
					// the case that this cell is not in the first row
					else
					{
						// the case that this cell is in the last column
						if(j==col)
						{
							counter += getHammingDistance(dna, map.get(i+1).get(j-1));
							counter += getHammingDistance(dna, map.get(i).get(j-1));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							return counter/3;
						}
						// the case that this cell is not in the last column
						else
						{
							counter += getHammingDistance(dna, map.get(i+1).get(j-1));
							counter += getHammingDistance(dna, map.get(i).get(j-1));
							counter += getHammingDistance(dna, map.get(i).get(j+1));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							return counter/4;
						}
					}
				}
				// the case that this cell is not in the first row
				else
				{
					// the case that this cell is in the last row
					if(i==row)
					{
						// the case that this cell is in the first column
						if(j==0)
						{	
							counter += getHammingDistance(dna, map.get(i-1).get(j));
							counter += getHammingDistance(dna, map.get(i).get(j+1));
							return counter/2;
						}
						// the case that this cell is not in the first column
						else
						{
							// the case that this cell is in the last column
							if(j==col)
							{
								counter += getHammingDistance(dna, map.get(i-1).get(j));
								counter += getHammingDistance(dna, map.get(i).get(j-1));
								counter += getHammingDistance(dna, map.get(i-1).get(j-1));
								return counter/3;

							}
							// the case that this cell is not in the last column
							else
							{
								counter += getHammingDistance(dna, map.get(i-1).get(j));
								counter += getHammingDistance(dna, map.get(i).get(j-1));
								counter += getHammingDistance(dna, map.get(i).get(j+1));
								counter += getHammingDistance(dna, map.get(i-1).get(j-1));
								return counter/4;
							}
						}
					}
					// the case that this cell is not in the last row
					else
					{
						// the case that this cell is in the first column
						if(j==0)
						{
							counter += getHammingDistance(dna, map.get(i-1).get(j));
							counter += getHammingDistance(dna, map.get(i).get(j+1));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							return counter/3;
						}
						// the case that this cell is not in the first column - it's in the last column
						else
						{
							counter += getHammingDistance(dna, map.get(i-1).get(j));
							counter += getHammingDistance(dna, map.get(i+1).get(j-1));
							counter += getHammingDistance(dna, map.get(i).get(j-1));
							counter += getHammingDistance(dna, map.get(i+1).get(j));
							counter += getHammingDistance(dna, map.get(i-1).get(j-1));
							return counter/5;
						}
					}
				}
			}
			// the case that this cell have all of the 8-usual neighbors
			else
			{
				counter += getHammingDistance(dna, map.get(i-1).get(j));
				counter += getHammingDistance(dna, map.get(i+1).get(j-1));
				counter += getHammingDistance(dna, map.get(i).get(j-1));
				counter += getHammingDistance(dna, map.get(i).get(j+1));
				counter += getHammingDistance(dna, map.get(i+1).get(j));
				counter += getHammingDistance(dna, map.get(i-1).get(j-1));
				return counter/6;			
			}
		}
	}

	/*
	 * this function update the cell i,j in the map and it's neighbors
	 * @param map - the current map
	 * @param i
	 * @param j
	 * @param vec - the input dna string
	 * @param first - indicates if this is the first circle or the second
	 * @param changed - a boolean matrix that holds true on the cell i,j if it was already updated
	 */
	private void updateNeighbour(ArrayList<ArrayList<String>> map,
			int i, int j, InputVector vec, boolean first, ArrayList<ArrayList<Boolean>> changed)
	{
		int row = rows - 1;
		int col = cols - 1;

		// this is the first circle update
		if(first)
		{
			ArrayList<ArrayList<Boolean>> changes = new ArrayList<ArrayList<Boolean>>();

			// update the cell istelf
			updateCell(map, i, j, first_percent, vec);

			ArrayList<Boolean> b;
			// for every cell (12*12 matrix)
			for (int i1 = 0; i1 < rows; i1++) 
			{
				b = new ArrayList<Boolean>();
				for (int j1 = 0; j1 < cols; j1++) 
				{
					// the case that this is the current cell that was changed
					if(i1 == i && j1==j)
					{
						b.add(true);
					}
					// the case that this isn't the current cell that was changed
					else
					{
						b.add(false);
					}
				}
				changes.add(b);
			}

			// the case that the representative is in an even row 
			if(i%2 == 0)
			{
				// the case that this cell don't have all of the 8-usual neighbors (an edge cell)
				if(i==0 || i==row || j==0 || j==col)
				{
					// the case that this cell is in the first row
					if(i==0)
					{
						// the case that this cell is in the first column
						if(j==0)
						{
							updateCell(map, i, j+1, secnd_percent, vec);
							updateCell(map, i+1, j+1, secnd_percent, vec);
							updateCell(map, i+1, j, secnd_percent, vec);
							ArrayList<Boolean> t2 = changes.get(i);
							ArrayList<Boolean> t3 = changes.get(i+1);
							t2.set(j+1, true);
							t3.set(j, true);
							t3.set(j+1, true);
							changes.set(i, t2);
							changes.set(i+1, t3);
							updateNeighbour(map, i, j+1, vec, false, changes);
							updateNeighbour(map, i+1, j+1, vec, false, changes);
							updateNeighbour(map, i+1, j, vec, false, changes);
						}
						// the case that this cell is not in the first row
						else
						{
							// the case that this cell is in the last column
							if(j==col)
							{
								updateCell(map, i+1, j, secnd_percent, vec);
								updateCell(map, i, j-1, secnd_percent, vec);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t2.set(j-1, true);
								t3.set(j, true);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i+1, j, vec, false, changes);
								updateNeighbour(map, i, j-1, vec, false, changes);
							}
							// the case that this cell is not in the last column
							else
							{
								updateCell(map, i, j+1, secnd_percent, vec);
								updateCell(map, i+1, j+1, secnd_percent, vec);
								updateCell(map, i+1, j, secnd_percent, vec);
								updateCell(map, i, j-1, secnd_percent, vec);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t2.set(j-1, true);
								t2.set(j+1, true);
								t3.set(j, true);
								t3.set(j+1, true);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i, j+1, vec, false, changes);
								updateNeighbour(map, i+1, j+1, vec, false, changes);
								updateNeighbour(map, i+1, j, vec, false, changes);
								updateNeighbour(map, i, j-1, vec, false, changes);
							}
						}
					}
					// the case that this cell is not in the first row
					else
					{
						// the case that this cell is in the last row
						if(i==row)
						{
							// the case that this cell is in the first column
							if(j==0)
							{
								updateCell(map, i-1, j, secnd_percent, vec);
								updateCell(map, i-1, j+1, secnd_percent, vec);
								updateCell(map, i, j+1, secnd_percent, vec);
								ArrayList<Boolean> t1 = changes.get(i-1);
								ArrayList<Boolean> t2 = changes.get(i);
								t1.set(j, true);
								t1.set(j+1, true);
								t2.set(j+1, true);
								changes.set(i-1, t1);
								changes.set(i, t2);
								updateNeighbour(map, i-1, j, vec, false, changes);
								updateNeighbour(map, i-1, j+1, vec, false, changes);
								updateNeighbour(map, i, j+1, vec, false, changes);
							}
							// the case that this cell is not in the first column
							else
							{
								// the case that this cell is in the last column
								if(j==col)
								{
									updateCell(map, i-1, j, secnd_percent, vec);
									updateCell(map, i, j-1, secnd_percent, vec);
									ArrayList<Boolean> t1 = changes.get(i-1);
									ArrayList<Boolean> t2 = changes.get(i);
									t1.set(j, true);
									t2.set(j-1, true);
									changes.set(i-1, t1);
									changes.set(i, t2);
									updateNeighbour(map, i-1, j, vec, false, changes);
									updateNeighbour(map, i, j-1, vec, false, changes);
								}
								// the case that this cell is not in the last column
								else
								{
									updateCell(map, i-1, j, secnd_percent, vec);
									updateCell(map, i-1, j+1, secnd_percent, vec);
									updateCell(map, i, j+1, secnd_percent, vec);
									updateCell(map, i, j-1, secnd_percent, vec);
									ArrayList<Boolean> t1 = changes.get(i-1);
									ArrayList<Boolean> t2 = changes.get(i);
									t1.set(j, true);
									t1.set(j+1, true);
									t2.set(j-1, true);
									t2.set(j+1, true);
									changes.set(i-1, t1);
									changes.set(i, t2);
									updateNeighbour(map, i-1, j, vec, false, changes);
									updateNeighbour(map, i-1, j+1, vec, false, changes);
									updateNeighbour(map, i, j+1, vec, false, changes);
									updateNeighbour(map, i, j-1, vec, false, changes);
								}
							}
						}
						// the case that this cell is not in the last row
						else
						{
							// the case that this cell is in the first column
							if(j==0)
							{
								updateCell(map, i-1, j, secnd_percent, vec);
								updateCell(map, i-1, j+1, secnd_percent, vec);
								updateCell(map, i, j+1, secnd_percent, vec);
								updateCell(map, i+1, j+1, secnd_percent, vec);
								updateCell(map, i+1, j, secnd_percent, vec);
								ArrayList<Boolean> t1 = changes.get(i-1);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t1.set(j, true);
								t1.set(j+1, true);
								t2.set(j+1, true);
								t3.set(j, true);
								t3.set(j+1, true);
								changes.set(i-1, t1);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i-1, j, vec, false, changes);
								updateNeighbour(map, i-1, j+1, vec, false, changes);
								updateNeighbour(map, i, j+1, vec, false, changes);
								updateNeighbour(map, i+1, j+1, vec, false, changes);
								updateNeighbour(map, i+1, j, vec, false, changes);
							}
							// the case that this cell is not in the first column - it's in the last column
							else
							{
								updateCell(map, i-1, j, secnd_percent, vec);
								updateCell(map, i+1, j, secnd_percent, vec);
								updateCell(map, i, j-1, secnd_percent, vec);
								ArrayList<Boolean> t1 = changes.get(i-1);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t1.set(j, true);
								t2.set(j-1, true);
								t3.set(j, true);
								changes.set(i-1, t1);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i-1, j, vec, false, changes);
								updateNeighbour(map, i+1, j, vec, false, changes);
								updateNeighbour(map, i, j-1, vec, false, changes);
							}
						}
					}
				}
				// the case that this cell have all of the 8-usual neighbors
				else
				{
					updateCell(map, i-1, j, secnd_percent, vec);
					updateCell(map, i-1, j+1, secnd_percent, vec);
					updateCell(map, i, j+1, secnd_percent, vec);
					updateCell(map, i+1, j+1, secnd_percent, vec);
					updateCell(map, i+1, j, secnd_percent, vec);
					updateCell(map, i, j-1, secnd_percent, vec);
					ArrayList<Boolean> t1 = changes.get(i-1);
					ArrayList<Boolean> t2 = changes.get(i);
					ArrayList<Boolean> t3 = changes.get(i+1);
					t1.set(j, true);
					t1.set(j+1, true);
					t2.set(j-1, true);
					t2.set(j+1, true);
					t3.set(j, true);
					t3.set(j+1, true);
					changes.set(i-1, t1);
					changes.set(i, t2);
					changes.set(i+1, t3);
					updateNeighbour(map, i-1, j, vec, false, changes);
					updateNeighbour(map, i-1, j+1, vec, false, changes);
					updateNeighbour(map, i, j+1, vec, false, changes);
					updateNeighbour(map, i+1, j+1, vec, false, changes);
					updateNeighbour(map, i+1, j, vec, false, changes);
					updateNeighbour(map, i, j-1, vec, false, changes);
				}
			}
			// the case that the representative is in an odd row 
			else	
			{
				// the case that this cell don't have all of the 8-usual neighbors (an edge cell)
				if(i==0 || i==row || j==0 || j==col)
				{
					// the case that this cell is in the first row
					if(i==0)
					{
						// the case that this cell is in the first column
						if(j==0)
						{
							updateCell(map, i, j+1, secnd_percent, vec);
							updateCell(map, i+1, j, secnd_percent, vec);
							ArrayList<Boolean> t2 = changes.get(i);
							ArrayList<Boolean> t3 = changes.get(i+1);
							t2.set(j+1, true);
							t3.set(j-1, true);
							changes.set(i, t2);
							changes.set(i+1, t3);
							updateNeighbour(map, i, j+1, vec, false, changes);
							updateNeighbour(map, i+1, j, vec, false, changes);
						}
						// the case that this cell is not in the first row
						else
						{
							// the case that this cell is in the last column
							if(j==col)
							{
								updateCell(map, i+1, j-1, secnd_percent, vec);
								updateCell(map, i, j-1, secnd_percent, vec);
								updateCell(map, i+1, j, secnd_percent, vec);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t2.set(j-1, true);
								t3.set(j-1, true);
								t3.set(j, true);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i+1, j-1, vec, false, changes);
								updateNeighbour(map, i, j-1, vec, false, changes);
								updateNeighbour(map, i+1, j, vec, false, changes);
							}
							// the case that this cell is not in the last column
							else
							{
								updateCell(map, i+1, j-1, secnd_percent, vec);
								updateCell(map, i, j-1, secnd_percent, vec);
								updateCell(map, i, j+1, secnd_percent, vec);
								updateCell(map, i+1, j, secnd_percent, vec);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t2.set(j-1, true);
								t2.set(j+1, true);
								t3.set(j-1, true);
								t3.set(j, true);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i+1, j-1, vec, false, changes);
								updateNeighbour(map, i, j-1, vec, false, changes);
								updateNeighbour(map, i, j+1, vec, false, changes);
								updateNeighbour(map, i+1, j, vec, false, changes);
							}
						}
					}
					// the case that this cell is not in the first row
					else
					{
						// the case that this cell is in the last row
						if(i==row)
						{
							// the case that this cell is in the first column
							if(j==0)
							{	
								updateCell(map, i-1, j, secnd_percent, vec);
								updateCell(map, i, j+1, secnd_percent, vec);
								ArrayList<Boolean> t1 = changes.get(i-1);
								ArrayList<Boolean> t2 = changes.get(i);
								t1.set(j, true);
								t2.set(j+1, true);
								changes.set(i-1, t1);
								changes.set(i, t2);
								updateNeighbour(map, i-1, j, vec, false, changes);
								updateNeighbour(map, i, j+1, vec, false, changes);
							}
							// the case that this cell is not in the first column
							else
							{
								// the case that this cell is in the last column
								if(j==col)
								{
									updateCell(map, i-1, j, secnd_percent, vec);
									updateCell(map, i, j-1, secnd_percent, vec);
									updateCell(map, i-1, j-1, secnd_percent, vec);
									ArrayList<Boolean> t1 = changes.get(i-1);
									ArrayList<Boolean> t2 = changes.get(i);
									t1.set(j-1, true);
									t1.set(j, true);
									t2.set(j-1, true);
									changes.set(i-1, t1);
									changes.set(i, t2);
									updateNeighbour(map, i-1, j, vec, false, changes);
									updateNeighbour(map, i, j-1, vec, false, changes);
									updateNeighbour(map, i-1, j-1, vec, false, changes);

								}
								// the case that this cell is not in the last column
								else
								{
									updateCell(map, i-1, j, secnd_percent, vec);
									updateCell(map, i, j-1, secnd_percent, vec);
									updateCell(map, i, j+1, secnd_percent, vec);
									updateCell(map, i-1, j-1, secnd_percent, vec);
									ArrayList<Boolean> t1 = changes.get(i-1);
									ArrayList<Boolean> t2 = changes.get(i);
									t1.set(j-1, true);
									t1.set(j, true);
									t2.set(j-1, true);
									t2.set(j+1, true);
									changes.set(i-1, t1);
									changes.set(i, t2);
									updateNeighbour(map, i-1, j, vec, false, changes);
									updateNeighbour(map, i, j-1, vec, false, changes);
									updateNeighbour(map, i, j+1, vec, false, changes);
									updateNeighbour(map, i-1, j-1, vec, false, changes);
								}
							}
						}
						// the case that this cell is not in the last row
						else
						{
							// the case that this cell is in the first column
							if(j==0)
							{
								updateCell(map, i-1, j, secnd_percent, vec);
								updateCell(map, i, j+1, secnd_percent, vec);
								updateCell(map, i+1, j, secnd_percent, vec);
								ArrayList<Boolean> t1 = changes.get(i-1);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t1.set(j, true);
								t2.set(j+1, true);
								t3.set(j, true);
								changes.set(i-1, t1);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i-1, j, vec, false, changes);
								updateNeighbour(map, i, j+1, vec, false, changes);
								updateNeighbour(map, i+1, j, vec, false, changes);
							}
							// the case that this cell is not in the first column - it's in the last column
							else
							{
								updateCell(map, i-1, j, secnd_percent, vec);
								updateCell(map, i+1, j-1, secnd_percent, vec);
								updateCell(map, i, j-1, secnd_percent, vec);
								updateCell(map, i+1, j, secnd_percent, vec);
								updateCell(map, i-1, j-1, secnd_percent, vec);
								ArrayList<Boolean> t1 = changes.get(i-1);
								ArrayList<Boolean> t2 = changes.get(i);
								ArrayList<Boolean> t3 = changes.get(i+1);
								t1.set(j-1, true);
								t1.set(j, true);
								t2.set(j-1, true);
								t3.set(j-1, true);
								t3.set(j, true);
								changes.set(i-1, t1);
								changes.set(i, t2);
								changes.set(i+1, t3);
								updateNeighbour(map, i-1, j, vec, false, changes);
								updateNeighbour(map, i+1, j-1, vec, false, changes);
								updateNeighbour(map, i, j-1, vec, false, changes);
								updateNeighbour(map, i+1, j, vec, false, changes);
								updateNeighbour(map, i-1, j-1, vec, false, changes);
							}
						}
					}
				}
				// the case that this cell have all of the 8-usual neighbors
				else
				{
					updateCell(map, i-1, j, secnd_percent, vec);
					updateCell(map, i+1, j-1, secnd_percent, vec);
					updateCell(map, i, j-1, secnd_percent, vec);
					updateCell(map, i, j+1, secnd_percent, vec);
					updateCell(map, i+1, j, secnd_percent, vec);
					updateCell(map, i-1, j-1, secnd_percent, vec);
					ArrayList<Boolean> t1 = changes.get(i-1);
					ArrayList<Boolean> t2 = changes.get(i);
					ArrayList<Boolean> t3 = changes.get(i+1);
					t1.set(j-1, true);
					t1.set(j, true);
					t2.set(j-1, true);
					t2.set(j+1, true);
					t3.set(j-1, true);
					t3.set(j, true);
					changes.set(i-1, t1);
					changes.set(i, t2);
					changes.set(i+1, t3);
					updateNeighbour(map, i-1, j, vec, false, changes);
					updateNeighbour(map, i+1, j-1, vec, false, changes);
					updateNeighbour(map, i, j-1, vec, false, changes);
					updateNeighbour(map, i, j+1, vec, false, changes);
					updateNeighbour(map, i+1, j, vec, false, changes);
					updateNeighbour(map, i-1, j-1, vec, false, changes);
				}
			}
		}

		/*
		 * the case that we want to update the second circle of neighbors -
		 * we only update a cell if it wasn't already updated in the past (according to the changed matrix)
		 */
		else
		{
			// the case that the representative is in an even row 
			if(i%2 == 0)
			{
				// the case that this cell don't have all of the 8-usual neighbors (an edge cell)
				if(i==0 || i==row || j==0 || j==col)
				{
					// the case that this cell is in the first row
					if(i==0)
					{
						// the case that this cell is in the first column
						if(j==0)
						{
							if(!changed.get(i).get(j+1))
							{
								updateCell(map, i, j+1, third_percent, vec);
							}
							if(!changed.get(i+1).get(j))
							{
								updateCell(map, i+1, j+1, third_percent, vec);
							}
							if(!changed.get(i+1).get(j))
							{
								updateCell(map, i+1, j, third_percent, vec);
							}
						}
						// the case that this cell is not in the first row
						else
						{
							// the case that this cell is in the last column
							if(j==col)
							{
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
								if(!changed.get(i).get(j-1))
								{
									updateCell(map, i, j-1, third_percent, vec);
								}
							}
							// the case that this cell is not in the last column
							else
							{
								if(!changed.get(i).get(j+1))
								{
									updateCell(map, i, j+1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j+1))
								{
									updateCell(map, i+1, j+1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
								if(!changed.get(i).get(j-1))
								{
									updateCell(map, i, j-1, third_percent, vec);
								}
							}
						}
					}
					// the case that this cell is not in the first row
					else
					{
						// the case that this cell is in the last row
						if(i==row)
						{
							// the case that this cell is in the first column
							if(j==0)
							{
								if(!changed.get(i-1).get(j))
								{
									updateCell(map, i-1, j, third_percent, vec);
								}
								if(!changed.get(i-1).get(j+1))
								{
									updateCell(map, i-1, j+1, third_percent, vec);
								}
								if(!changed.get(i).get(j+1))
								{
									updateCell(map, i, j+1, third_percent, vec);
								}
							}
							// the case that this cell is not in the first column
							else
							{
								// the case that this cell is in the last column
								if(j==col)
								{
									if(!changed.get(i-1).get(j))
									{
										updateCell(map, i-1, j, third_percent, vec);
									}
									if(!changed.get(i).get(j-1))
									{
										updateCell(map, i, j-1, third_percent, vec);
									}
								}
								// the case that this cell is not in the last column
								else
								{
									if(!changed.get(i-1).get(j))
									{
										updateCell(map, i-1, j, third_percent, vec);
									}
									if(!changed.get(i-1).get(j+1))
									{
										updateCell(map, i-1, j+1, third_percent, vec);
									}
									if(!changed.get(i).get(j+1))
									{
										updateCell(map, i, j+1, third_percent, vec);
									}
									if(!changed.get(i).get(j-1))
									{
										updateCell(map, i, j-1, third_percent, vec);
									}
								}
							}
						}
						// the case that this cell is not in the last row
						else
						{
							// the case that this cell is in the first column
							if(j==0)
							{
								if(!changed.get(i-1).get(j))
								{
									updateCell(map, i-1, j, third_percent, vec);
								}
								if(!changed.get(i-1).get(j+1))
								{
									updateCell(map, i-1, j+1, third_percent, vec);
								}
								if(!changed.get(i).get(j+1))
								{
									updateCell(map, i, j+1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j+1))
								{
									updateCell(map, i+1, j+1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
							}
							// the case that this cell is not in the first column - it's in the last column
							else
							{
								if(!changed.get(i-1).get(j))
								{
									updateCell(map, i-1, j, third_percent, vec);
								}
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
								if(!changed.get(i).get(j-1))
								{
									updateCell(map, i, j-1, third_percent, vec);
								}
							}
						}
					}
				}
				// the case that this cell have all of the 8-usual neighbors
				else
				{
					if(!changed.get(i-1).get(j))
					{
						updateCell(map, i-1, j, third_percent, vec);
					}
					if(!changed.get(i-1).get(j+1))
					{
						updateCell(map, i-1, j+1, third_percent, vec);
					}
					if(!changed.get(i).get(j+1))
					{
						updateCell(map, i, j+1, third_percent, vec);
					}
					if(!changed.get(i+1).get(j+1))
					{
						updateCell(map, i+1, j+1, third_percent, vec);
					}
					if(!changed.get(i+1).get(j))
					{
						updateCell(map, i+1, j, third_percent, vec);
					}
					if(!changed.get(i).get(j-1))
					{
						updateCell(map, i, j-1, third_percent, vec);
					}
				}
			}
			// the case that the representative is in an odd row 
			else	
			{
				// the case that this cell don't have all of the 8-usual neighbors (an edge cell)
				if(i==0 || i==row || j==0 || j==col)
				{
					// the case that this cell is in the first row
					if(i==0)
					{
						// the case that this cell is in the first column
						if(j==0)
						{
							if(!changed.get(i).get(j+1))
							{
								updateCell(map, i, j+1, third_percent, vec);
							}
							if(!changed.get(i+1).get(j))
							{
								updateCell(map, i+1, j, third_percent, vec);
							}
						}
						// the case that this cell is not in the first row
						else
						{
							// the case that this cell is in the last column
							if(j==col)
							{
								if(!changed.get(i+1).get(j-1))
								{
									updateCell(map, i+1, j-1, third_percent, vec);
								}
								if(!changed.get(i).get(j-1))
								{
									updateCell(map, i, j-1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
							}
							// the case that this cell is not in the last column
							else
							{
								if(!changed.get(i+1).get(j-1))
								{
									updateCell(map, i+1, j-1, third_percent, vec);
								}
								if(!changed.get(i).get(j-1))
								{
									updateCell(map, i, j-1, third_percent, vec);
								}
								if(!changed.get(i).get(j+1))
								{
									updateCell(map, i, j+1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
							}
						}
					}
					// the case that this cell is not in the first row
					else
					{
						// the case that this cell is in the last row
						if(i==row)
						{
							// the case that this cell is in the first column
							if(j==0)
							{	
								if(!changed.get(i-1).get(j))
								{
									updateCell(map, i-1, j, third_percent, vec);
								}
								if(!changed.get(i).get(j+1))
								{
									updateCell(map, i, j+1, third_percent, vec);
								}
							}
							// the case that this cell is not in the first column
							else
							{
								// the case that this cell is in the last column
								if(j==col)
								{
									if(!changed.get(i-1).get(j))
									{
										updateCell(map, i-1, j, third_percent, vec);
									}
									if(!changed.get(i).get(j-1))
									{
										updateCell(map, i, j-1, third_percent, vec);
									}
									if(!changed.get(i-1).get(j-1))
									{
										updateCell(map, i-1, j-1, third_percent, vec);
									}
								}
								// the case that this cell is not in the last column
								else
								{
									if(!changed.get(i-1).get(j))
									{
										updateCell(map, i-1, j, third_percent, vec);
									}
									if(!changed.get(i).get(j-1))
									{
										updateCell(map, i, j-1, third_percent, vec);
									}
									if(!changed.get(i).get(j+1))
									{
										updateCell(map, i, j+1, third_percent, vec);
									}
									if(!changed.get(i-1).get(j-1))
									{
										updateCell(map, i-1, j-1, third_percent, vec);
									}
								}
							}
						}
						// the case that this cell is not in the last row
						else
						{
							// the case that this cell is in the first column
							if(j==0)
							{
								if(!changed.get(i-1).get(j))
								{
									updateCell(map, i-1, j, third_percent, vec);
								}
								if(!changed.get(i).get(j+1))
								{
									updateCell(map, i, j+1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
							}
							// the case that this cell is not in the first column - it's in the last column
							else
							{
								if(!changed.get(i-1).get(j))
								{
									updateCell(map, i-1, j, third_percent, vec);
								}
								if(!changed.get(i+1).get(j-1))
								{
									updateCell(map, i+1, j-1, third_percent, vec);
								}
								if(!changed.get(i).get(j-1))
								{
									updateCell(map, i, j-1, third_percent, vec);
								}
								if(!changed.get(i+1).get(j))
								{
									updateCell(map, i+1, j, third_percent, vec);
								}
								if(!changed.get(i-1).get(j-1))
								{
									updateCell(map, i-1, j-1, third_percent, vec);
								}
							}
						}
					}
				}
				// the case that this cell have all of the 8-usual neighbors
				else
				{
					if(!changed.get(i-1).get(j))
					{
						updateCell(map, i-1, j, third_percent, vec);
					}
					if(!changed.get(i+1).get(j-1))
					{
						updateCell(map, i+1, j-1, third_percent, vec);
					}
					if(!changed.get(i).get(j-1))
					{
						updateCell(map, i, j-1, third_percent, vec);
					}
					if(!changed.get(i).get(j+1))
					{
						updateCell(map, i, j+1, third_percent, vec);
					}
					if(!changed.get(i+1).get(j))
					{
						updateCell(map, i+1, j, third_percent, vec);
					}
					if(!changed.get(i-1).get(j-1))
					{
						updateCell(map, i-1, j-1, third_percent, vec);
					}
				}
			}
		}
	}

	/*
	 * this function update the i,j cell in the map towords a certain percent to the dna string from vec
	 * @param map - the current map
	 * @param i
	 * @param j
	 * @param percent - the percent to change
	 * @param vec - input vector
	 */
	private void updateCell(ArrayList<ArrayList<String>> map, int i, int j, int percent, InputVector vec)
	{
		String dna = vec.getDna();
		ArrayList<String> tempList = map.get(i);
		String temp = tempList.get(j);
		String temp2 = "";
		ArrayList<Integer> mismatch = new ArrayList<Integer>();
		ArrayList<Integer> mismatch2 = new ArrayList<Integer>();

		// for every char in the dna string
		for (int k = 0; k < dna_size; k++)
		{
			// the case that this char is different between the strings
			if(dna.charAt(k) != temp.charAt(k))
			{
				mismatch.add(k);
			}
		}

		// shuffle the mismatches indexes array - to create random effect
		Collections.shuffle(mismatch);

		float lengtht = (mismatch.size()*((float)percent/100));
		int length = Math.round(lengtht);

		// take the first percent indexes from the shuffled mismatch array
		for (int k = 0; k < length; k++) 
		{
			mismatch2.add(mismatch.get(k));
		}


		// for every char in the dna string
		for (int k = 0; k < dna_size; k++)
		{
			// the case that this char needs to be changed
			if(mismatch2.contains(k))
			{
				temp2 += dna.charAt(k);
			}
			// the case that this char doesn't need to be changed
			else
			{
				temp2+= temp.charAt(k);
			}
		}

		tempList.set(j, temp2);
		map.set(i, tempList);
	}

	/*
	 * this function find the representative of the input vector from the map
	 * @param vec - the input vector
	 * @param map - the current map
	 * @return - the indexes of the chosen representative
	 */
	private ArrayList<Integer> findRepresente(InputVector vec, ArrayList<ArrayList<String>> map)
	{
		ArrayList<Integer> loc = new ArrayList<Integer>();
		String dna = vec.getDna();
		int min = 101;
		int min_i = 0;
		int min_j = 0;
		int count;

		// for every cell in the map
		for (int i = 0; i < rows; i++) 
		{
			for (int j = 0; j < cols; j++) 
			{
				// find the distance
				count = getHammingDistance(dna, map.get(i).get(j));
				// the case that this is the minimum distance
				if(count < min)
				{
					min = count;
					min_i = i;
					min_j = j;
				}
			}
		}

		loc.add(min_i);
		loc.add(min_j);
		return loc;
	}

	/**
	 * this function initialize a random map - 12*12 dna strings of length 100 matrix
	 * @return
	 */
	public ArrayList<ArrayList<String>> init_map()
	{
		ArrayList<ArrayList<String>> map = new ArrayList<ArrayList<String>>();
		Random r = new Random();
		int A = 0, T = 1, C = 2, G = 3;
		int num;
		String dna = "";
		ArrayList<String> dna_rows;

		// for every cell in the map
		for (int i = 0; i < rows; i++) 
		{
			dna_rows = new ArrayList<String>();	
			for (int j = 0; j < cols; j++) 
			{
				// for every char in the dna string
				for (int k = 0; k < dna_size; k++) 
				{
					num = r.nextInt(4);

					// the case that we picked an 'A' char
					if(num == A)
					{
						dna+="A";
					}
					// the case that we picked an 'T' char
					else if(num == T)
					{
						dna+="T";
					}
					// the case that we picked an 'C' char
					else if(num == C)
					{
						dna+="C";
					}
					// the case that we picked an 'G' char
					else if(num == G)
					{
						dna+="G";
					}
				}
				dna_rows.add(dna);
				dna = "";
			}
			map.add(dna_rows);
		}
		return map;
	}


	/**
	 * this function return the hamming distance between two strings
	 * @param compOne - the first string
	 * @param compTwo - the second string
	 * @return - the distance between the strings
	 */
	public int getHammingDistance(String compOne, String compTwo)
	{
		// the case that this strings don't have the same length
		if (compOne.length() != compTwo.length())
		{
			return -1;
		}

		int counter = 0;
		// for every char in the strings
		for (int i = 0; i < compOne.length(); i++)
		{
			// the case that this string have different chars at this index
			if (compOne.charAt(i) != compTwo.charAt(i)) counter++;
		}
		return counter;
	}
}
